package com.chezai.client.global.location;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;

import com.chezai.client.bean.GpsStatusEntity;
import com.chezai.client.bean.LatLng;
import com.chezai.client.bean.SecondLocation;
import com.chezai.client.global.Const;
import com.chezai.client.global.DeviceFactory;
import com.chezai.client.global.LogService;
import com.chezai.client.global.PolygonManager;
import com.chezai.client.global.TerminalApplication;
import com.chezai.client.global.location.rx.InitialValueObservable;
import com.chezai.client.global.location.rx.RxLocation;
import com.chezai.client.utils.DateUtil;
import com.chezai.client.utils.LocationUtils;
import com.chezai.client.utils.PreferenceManager;
import com.orhanobut.logger.Logger;

import org.greenrobot.eventbus.EventBus;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;

/**
 * 定位
 * Created by likunyang on 2017/9/18.
 */

public class LocationMgr implements GpsStatus.Listener {
    private static final int GPS_EFFECTIVE_COUNT = 3;//最少搜星数
    private static final double MAX_SPEED = 63.36;//km/h
    private static final int MAX_TIME_INVALITE = 5 * 60;//定位无效最长时间 单位：秒
    private static final int MAX_TIME_OUT_POLYGON = 60;//定位无效最长时间 单位：秒
    private static String TAG = LocationMgr.class.getSimpleName();
    private static LocationMgr newInstance;
    private static Location mPreBestLocation;//上一次最好定位
    private final String imei;
    private InitialValueObservable<Location> observable;
    private LocationManager lm;
    private boolean isOpenLocLog;
    private long lastBestLocTime;//最后一次定位最好的时间
    private boolean isColdStart = false;//是否冷启动
    private Location isFirstOutPolygonLoc;//第一次出围栏时间

    private LocationMgr(Context context) {
        imei = DeviceFactory.getInstance().getHardware().getImei(TerminalApplication.mContext);
        isOpenLocLog = PreferenceManager.getBoolean(Const.SP_IS_OPEN_LOC_LOG, false);
        initLocationManager(context);
    }

    public static synchronized LocationMgr getInstance() {
        if (newInstance == null) {
            newInstance = new LocationMgr(TerminalApplication.mContext);
        }
        return newInstance;
    }


    @SuppressLint("DefaultLocale")
    private static String locationToStr(Location l) {
        StringBuilder s = new StringBuilder();
        s.append("Location[");
        s.append(l.getProvider());
        s.append(String.format(" %.6f,%.6f", l.getLongitude(), l.getLatitude()));
        s.append(String.format(" hAcc=%.1f", l.getAccuracy()));
        s.append(" speed=" + l.getSpeed());
        s.append(" time=" + DateUtil.timestampStr(l.getTime(), DateUtil.sf7));
        s.append(']');
        return s.toString();
    }

    public static Location getBestLocation() {
        return mPreBestLocation;
    }

    private void initLocationManager(Context context) {
        lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        if (ActivityCompat.checkSelfPermission(TerminalApplication.mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(TerminalApplication.mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        lm.addGpsStatusListener(this);
        if (isEnableGps()) {
            observable = RxLocation.locationChanges(lm);
        } else {
            throw new RuntimeException("GPS 无法使用");
        }
    }

    //获取最好定位
    @SuppressLint("CheckResult")
    public void bestSubscribe() {
        observable.filter(new Predicate<Location>() {  //瞬时速度
            @Override
            public boolean test(Location location) throws Exception {
                if (TextUtils.equals(Const.INIT_PROVIDER, location.getProvider())) {
                    Logger.t(TAG).e("test: 当前定位为INIT DATA");
                    mPreBestLocation = location;
                    return false;
                }
                if (getStatellites() <= 3) {
                    Logger.t(TAG).e("test: 当卫星数为 小于等于 3");
                    return false;
                }
                if (location.hasSpeed()) {
                    Logger.t(TAG).e("test: 当前定位有速度");
                    return LocationUtils.conversionSpeedKmH(location.getSpeed()) < MAX_SPEED;
                }
                return true;
            }
        }).filter(new Predicate<Location>() {
                      @Override
                      public boolean test(Location location) throws Exception {
                          if (!Const.IS_TRAINING) {
                              Logger.t(TAG).e("test: 未练车");
                              return true;
                          }
                          LatLng latLng = new LatLng(location.getLongitude(), location.getLatitude());
                          if (!PolygonManager.getInstance().latLngInPolygon(latLng)) {
                              Logger.t(TAG).e("test: 定位不在围栏内 lat" + latLng.getLat() + " lng" + latLng.getLng());
                              if (isFirstOutPolygonLoc == null) {
                                  Logger.t(TAG).e("test: 定位第一次不在围栏内");
                                  isFirstOutPolygonLoc = location;
                              } else {
                                  long time = (location.getTime() - isFirstOutPolygonLoc.getTime()) / 1000;
                                  return time >= MAX_TIME_OUT_POLYGON;
                              }
                              return false;
                          }
                          isFirstOutPolygonLoc = null;
                          return true;
                      }
                  }
        ).filter(new Predicate<Location>() { //精度
            @Override
            public boolean test(Location location) throws Exception {
                Logger.t(TAG).e("test: 精度校验：" + location.getAccuracy());
                if (getStatellites() > 7) {
                    return location.getAccuracy() <= 5;
                } else {
                    return location.getAccuracy() <= 10;
                }
            }
        }).filter(new Predicate<Location>() { //平均速度
            @Override
            public boolean test(Location location) throws Exception {
                boolean result;
                if (TextUtils.equals(Const.INIT_PROVIDER, mPreBestLocation.getProvider())) {
                    result = true;
                } else {
                    long time = (location.getTime() - mPreBestLocation.getTime()) / 1000;
                    float distance = distance(mPreBestLocation, location);
                    float speed = (distance / time) * 3.6f;
                    Logger.t(TAG).e("test: 平均速度校验->" + speed);
                    result = speed < MAX_SPEED;
                }
                if (Const.IS_TRAINING) {
                    if (result) {
                        EventBus.getDefault().post(new SecondLocation(location));
                    } else {
                        EventBus.getDefault().post(new SecondLocation(mPreBestLocation));
                    }
                }
                return result;
            }
        }).buffer(10, TimeUnit.SECONDS)
                .map(new Function<List<Location>, Location>() {
                    @Override
                    public Location apply(List<Location> list) throws Exception {
                        Location location = new Location(Const.INIT_PROVIDER);
                        if (list.size() > 0) {
                            location = list.get(0);
                            for (int i = 1; i < list.size(); i++) {
                                Location current = list.get(i);
                                if (current.getAccuracy() < location.getAccuracy()) {
                                    location = current;
                                }
                            }
                        }
                        return location;
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Location>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onNext(Location loc) {
                        locationProcess(loc);
                    }


                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }

    private void locationProcess(Location loc) {
        if (!TextUtils.equals(Const.INIT_PROVIDER, loc.getProvider())) { //有最佳定位
            mPreBestLocation = loc;
            PreferenceManager.putString(Const.SP_BEST_LAT, String.valueOf(mPreBestLocation.getLatitude()));
            PreferenceManager.putString(Const.SP_BEST_LON, String.valueOf(mPreBestLocation.getLongitude()));
            resetRebootDeviceCondition();
        } else { //没有最佳定位
            if (Const.IS_TRAINING) {
                if (lastBestLocTime == 0) {
                    lastBestLocTime = System.nanoTime();
                } else {
                    long currentTime = System.nanoTime();
                    double time = (currentTime - lastBestLocTime) * Math.pow(10, -9);
                    if (time >= MAX_TIME_INVALITE && !isColdStart) {
                        isColdStart = true;
                        gpsColdStart();
                    }
                }
            } else {
                resetRebootDeviceCondition();
            }
        }
        if (isOpenLocLog) {
            String content = "[ State Count:" + getStatellites() + " | " + locationToStr(loc) + " is best "+ !TextUtils.equals(Const.INIT_PROVIDER, loc.getProvider()) +"]";
            LogService.getInstance(TerminalApplication.mContext).putLogContent("location/" + imei, content, true);
        }
        if (Const.IS_TRAINING && LocationManager.GPS_PROVIDER.equals(loc.getProvider())) {//有最佳定位，进出围栏报警
            EventBus.getDefault().post(new LatLng(loc.getLongitude(), loc.getLatitude()));
        }
        EventBus.getDefault().post(mPreBestLocation);
    }

    @Override
    public void onGpsStatusChanged(int event) {
        switch (event) {
            case GpsStatus.GPS_EVENT_FIRST_FIX: //第一次定位
                break;
            case GpsStatus.GPS_EVENT_SATELLITE_STATUS: //卫星状态改变
                int count = getStatellites();
                GpsStatusEntity entity = new GpsStatusEntity();
                if (Const.IS_TRAINING) {
                    if (isColdStart && count > 0) {
                        resetRebootDeviceCondition();
                    }
                    entity.satelliteCount = count;
                    entity.status = count > GPS_EFFECTIVE_COUNT ? GpsStatusEntity.GPS_EFFECTIVE : GpsStatusEntity.GPS_INVALID;
                    EventBus.getDefault().post(entity);
                }
                break;
            case GpsStatus.GPS_EVENT_STARTED://定位启动
                resetRebootDeviceCondition();
                break;
            case GpsStatus.GPS_EVENT_STOPPED://定位结束
                break;
        }
    }

    //还原定位无效，重启设备判断条件初始数据
    public void resetRebootDeviceCondition() {
        this.lastBestLocTime = 0;
        this.isColdStart = true;
    }

    public float distance(Location l1, Location l2) {
        float[] result = new float[1];
        Location.distanceBetween(l1.getLatitude(), l1.getLongitude()
                , l2.getLatitude(), l2.getLongitude(), result);
        return result[0];
    }

    public int getAltitude() {
        return Double.valueOf(mPreBestLocation.getAltitude()).intValue();
    }

    public double getCurrentLat() {
        return mPreBestLocation.getLatitude();
    }

    public double getCurrentLon() {
        return mPreBestLocation.getLongitude();
    }

    private int getStatellites() {
        int count = 0;
        if (ActivityCompat.checkSelfPermission(TerminalApplication.mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return count;
        }
        GpsStatus gpsStatus = lm.getGpsStatus(null); //获取当前状态
        int maxSatellites = gpsStatus.getMaxSatellites();//获取卫星颗数的默认最大值
        Iterator<GpsSatellite> iteres = gpsStatus.getSatellites().iterator(); //创建一个迭代器保存所有卫星
        while (iteres.hasNext() && count <= maxSatellites) {
            GpsSatellite s = iteres.next();
            if (s.usedInFix()) {
                count++;
            }
        }
        return count;
    }


    public float getSpeed() {
        return mPreBestLocation.getSpeed();
    }

    public void setOpenLocLog(boolean b) {
        this.isOpenLocLog = b;
    }

    /**
     * GPS定位是否可用
     *
     * @return true 可用，false 不可用
     */
    private boolean isEnableGps() {
        return lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
    }

    /**
     * 冷启动
     *
     * @return true 为冷启动
     */
    private boolean gpsColdStart() {
        if (Const.QHYC_C103.equals(TerminalApplication.channelModel)) {
            try {
                Bundle bundleCold = null;
                lm.sendExtraCommand(LocationManager.GPS_PROVIDER, "force_xtra_injection", bundleCold); //command
                lm.sendExtraCommand(LocationManager.GPS_PROVIDER, "force_time_injection", bundleCold);
                return lm.sendExtraCommand(LocationManager.GPS_PROVIDER, "delete_aiding_data", bundleCold);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        resetRebootDeviceCondition();
        return false;
    }
}
